package modbus

import (
	"Metro/dal/query"
	"Metro/protocol"
	"bytes"
	"encoding/binary"
	"encoding/hex"
	"encoding/json"
	"errors"
	"fmt"
	"github.com/panjf2000/gnet/v2"
	"jialiking.com/gaogang/common/utils"
	"math"
	"strconv"
	"strings"
	"sync"
	"time"
)

const ModbusTypeID = 112652351242253

var Server = server{}

type server struct {
	gnet.BuiltinEventEngine
	Eng           *gnet.Engine
	MacAdapterMap *sync.Map
}

type header struct {
	Mac         [9]byte
	Transaction uint16
	Protocol    uint16
	Length      uint16
	Addr        uint8
	Func        uint8
	DataLength  uint8
}

type packet struct {
	header
	Data []byte
}

func init() {
	Server.MacAdapterMap = &sync.Map{}
}

func getRange(r string) (int, int, error) {
	indexes := strings.Split(r, "-")
	if len(indexes) == 2 {
		start, e := strconv.Atoi(indexes[0])
		if e != nil {
			return -1, -1, errors.New("convert start index error")
		}
		end, e := strconv.Atoi(indexes[1])
		if e != nil {
			return -1, -1, errors.New("convert end index error")
		}
		if start < 0 {
			return -1, -1, errors.New("start index less than 0")
		}
		if end < start {
			return -1, -1, errors.New("end index less than start index")
		}
		return start, end, nil
	} else {
		return -1, -1, errors.New("invalid range")
	}
}

func bytesToInt64(b []byte) (int64, error) {
	var val int64

	if len(b) > 8 {
		return 0, errors.New("input bytes length > 8")
	}

	arr := make([]byte, 8)
	copy(arr[8-len(b):], b)

	val = int64(binary.BigEndian.Uint64(arr))

	if len(b) > 0 && (b[0]&0x80) != 0 {
		val -= 1 << (len(b) * 8)
	}

	return val, nil
}

// device bit类型 原始数据顺序按   每个字节独立 递减排序 7 6 5 4 3 2 1 0, 15 14 13 12 11 10 9 8
// start,end 定义的顺序按   	    整个数据 递增排序 0 1 2 3 4 5 6 7, 8 9 10 11 12 13 14 15
func getUint64(data []byte, startBit, endBit int) uint64 {
	var result uint64
	bitLength := endBit - startBit + 1

	for i := 0; i < bitLength; i++ {
		currentBit := startBit + i
		byteIndex := currentBit / 8
		bitOffset := 7 - currentBit%8
		bit := (data[byteIndex] >> bitOffset) & 1
		result |= uint64(bit) << (bitLength - 1 - i)
	}
	return result
}

func getBytes(data []byte, startBit, endBit int) []byte {
	startByte := startBit / 8
	endByte := endBit / 8
	startOffset := startBit % 8
	endOffset := endBit % 8

	if startByte == endByte {
		return []byte{data[startByte] << startOffset >> startOffset >> (7 - endOffset)}
	} else {
		result := make([]byte, endByte-startByte+1)
		result[0] = data[startByte] << startOffset >> startOffset
		for i := 1; i < endByte-startByte; i++ {
			result[i] = data[startByte+i]
		}
		result[endByte-startByte] = data[endByte] >> (7 - endOffset)
		return result
	}
}

func (server server) OnBoot(eng gnet.Engine) gnet.Action {
	server.Eng = &eng
	return gnet.None
}

func (server server) OnOpen(c gnet.Conn) (out []byte, action gnet.Action) {
	utils.GlobalLog("modbus connection: %s", c.RemoteAddr().String())
	return
}

func (server server) OnTraffic(c gnet.Conn) gnet.Action {
	for {
		var p packet
		var err error
		if p, err = decode(c); errors.Is(err, protocol.ErrIncompletePacket) {
			break
		} else if errors.Is(err, protocol.ErrInvalidPacket) {
			utils.GlobalLog("invalid packet: %v", err)
			return gnet.Close
		} else if err != nil {
			utils.GlobalLog("unknown error: %v", err)
			return gnet.Close
		}
		process(c, p)
	}
	return gnet.None
}

func (server server) OnClose(c gnet.Conn, e error) (action gnet.Action) {
	utils.GlobalLog("modbus disconnection: %s, %v", c.RemoteAddr().String(), e)
	return
}

func (server server) OnTick() (delay time.Duration, action gnet.Action) {
	return 1 * time.Minute, action
}

func decode(c gnet.Conn) (p packet, err error) {
	var buf []byte
	buf, _ = c.Peek(18)
	if len(buf) < 18 {
		return p, protocol.ErrIncompletePacket
	}
	err = binary.Read(bytes.NewReader(buf), binary.BigEndian, &p.header)
	if err != nil {
		return
	}
	if p.Protocol != 0 {
		return p, protocol.ErrInvalidPacket
	}

	buf, _ = c.Peek(18 + int(p.Length-3))
	if len(buf) < 18+int(p.Length-3) {
		return p, protocol.ErrIncompletePacket
	}
	p.Data = make([]byte, int(p.Length-3))
	err = binary.Read(bytes.NewReader(buf[18:]), binary.BigEndian, &p.Data)
	if err != nil {
		return
	}
	_, _ = c.Discard(18 + int(p.Length-3))
	return
}

func process(c gnet.Conn, p packet) {
	utils.InfoLog("packet:%s %v", p.Mac, p.Data)
	protocol.UpdateHostOnline(string(p.Mac[:]))
	hosts, err := query.Host.Preload(query.Host.Devices.ModbusDeviceDefinition.ModbusDeviceDataPoints).Where(query.Host.Gateway.Eq(string(p.Mac[:]))).Find()
	for _, host := range hosts {
		if host == nil {
			utils.ErrorLog("query host error: mac %v", string(p.Mac[:]), err)
			return
		}
		for _, device := range host.Devices {
			if device.TypeID != ModbusTypeID {
				utils.ErrorLog("device type error: %v, skip;", device.TypeID)
				continue
			}

			ranges := strings.Split(device.Addr, ",")
			data := make([]byte, 0, len(p.Data))
			for _, r := range ranges {
				start, end, e := getRange(r)
				if e != nil {
					utils.ErrorLog("get range error: %s", e.Error())
					return
				}
				if end >= len(p.Data)*8 {
					utils.ErrorLog("end index out of range: %v", end)
					return
				}
				data = append(data, getBytes(p.Data, start, end)...)
				//data = append(data, p.Data[start:end+1]...)
			}

			result := make(map[string]float64)
			for _, dp := range device.ModbusDeviceDefinition.ModbusDeviceDataPoints {
				start, end, e := getRange(dp.Range)
				if e != nil {
					utils.ErrorLog("get range error for point: %s", e.Error())
					return
				}

				if dp.Bit {
					if end/8 >= len(data) {
						utils.ErrorLog("end index out of range for point: %v", end)
						return
					}

					value := getUint64(data, start, end)
					result[dp.Key] = dp.Multi*float64(value) + dp.Base
					utils.InfoLog("point: %s, value: %v", dp.Key, result[dp.Key])
				} else {
					if end >= len(data) {
						utils.ErrorLog("end index out of range for point: %v", end)
						return
					}
					value, err := bytesToInt64(data[start : end+1])
					if err != nil {
						utils.ErrorLog("convert bytes to int64 error: %v", err)
						return
					}
					result[dp.Key] = math.Round((dp.Multi*float64(value)+dp.Base)*100) / 100
					utils.InfoLog("point: %s, value: %v, hex: %s", dp.Key, result[dp.Key], hex.EncodeToString(data[start:end+1]))
				}
			}

			dataBytes, err := json.Marshal(result)
			if err != nil {
				utils.ErrorLog("json 序列化失败: %s", err.Error())
				return
			}
			_, err = query.Device.Where(query.Device.ID.Eq(device.ID)).Updates(map[string]any{
				"LastReportDate": time.Now(),
				"LastReportData": dataBytes,
			})
			if err != nil {
				utils.ErrorLog("update host error: %v", err.Error())
				return
			}

			//遍历result
			for key, value := range result {
				thresholds, _ := query.Threshold.Where(query.Threshold.DefinitionID.Eq(*device.DefinitionID), query.Threshold.Field.Eq(key)).Find()
				if thresholds != nil {
					for _, threshold := range thresholds {
						floatValue, err := strconv.ParseFloat(threshold.Value, 64)
						if err != nil {
							utils.ErrorLog("Error converting string to float:", err)
							continue
						}
						if threshold.Comparison == "=" && floatValue == value ||
							threshold.Comparison == ">" && value > floatValue ||
							threshold.Comparison == "<" && value < floatValue ||
							threshold.Comparison == "!=" && value != floatValue {
							//触发报警
							protocol.TriggerEvent(device, threshold, fmt.Sprintf("%f", value))
						}

						if threshold.Comparison == "=" && floatValue != value ||
							threshold.Comparison == ">" && value <= floatValue ||
							threshold.Comparison == "<" && value >= floatValue ||
							threshold.Comparison == "!=" && value == floatValue {
							//触发报警
							protocol.ClearEvent(device, threshold, fmt.Sprintf("%f", value))
						}
					}
				}
			}
		}
	}

}
